// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using EnvDTE;
using System.Collections.Generic;

namespace Microsoft.VSPowerToys.VSCmdShell
{
	/// <summary>
	/// ShellTextBox is an implementation of RichTextBox that behaves similar to a command shell window.
    /// Its primary purpose is to host one of IShellHost implementations and allow user to intearc with the hosted shell.
   	/// </summary>
    [GuidAttribute("D1DE0220-67BE-48c4-8D88-19780F710A00")]
    [System.Runtime.InteropServices.ComVisible(true)]
    public sealed class ShellTextBox : RichTextBox, IShellUI
	{
		#region Global variables
		
        private delegate void UpdateTextHandler(string text);

        private _DTE extensibilityObject;
		
        private StringCollection matchDirectoryForStrings;

        private StringBuilder buffer = new StringBuilder(1024);

        /// <summary>
        /// Reference to Visual Studio extensibility object.
        /// </summary>
		public _DTE ExtensibilityObject
		{
			get
			{
				return extensibilityObject;
			}

			set
			{
				extensibilityObject = value;
                this.ApplyChangesFontsAndColors();
			}
		}

        /// <summary>
        /// Command browser to list possible entries for Visual Studio commands
        /// </summary>
		private Browser commandBrowser; 
		
       
        /// <summary>
        /// A collection of previously issued commands.
        /// </summary>
        private CommandHistoryCollection history     = new CommandHistoryCollection();
		
        /// <summary>
        /// A collection of possible auto complete entries.
        /// </summary>
        private AutoCompleteCollection autoList;
		
        /// <summary>
        /// Index where the current command line starts. User is not allowed to type before this index.
        /// </summary>
        private int CurrentCommandStartIndex;

        
        /// <summary>
        /// A copy of the last appended line, in case of some shells this can
        /// be used to parse current directory;
        /// </summary>
        private string LastAppendedLine;

        /// <summary>
        /// This is the index where command browsers was shown for the last time.
        /// Only the text after this index will be replaced.
        /// </summary>
        private int CommandBrowserStartIndex;

        /// <summary>
        /// Interface to interact with hosted shell.
        /// </summary>
        private IShellHost shellHost;

        /// <summary>
        /// If set to true, it means user is currently going through auto complete list by repeated presseing Tab or Shift + Tab
        /// </summary>
        private bool inAutoCompleteCycle;

        /// <summary>
        /// Sync object used to lock textbox data entry
        /// </summary>
        private object syncObject = new Object();

        /// <summary>
        /// Reset event to notify a thread that a line is read from the user.
        /// </summary>
        private System.Threading.ManualResetEvent lineCompleted = new System.Threading.ManualResetEvent(false);

        /// <summary>
        /// Buffer holding lines read from user to be used in ReadLine implementation.
        /// </summary>
        /// <remarks>Collection is not guareented to be thread safe</remarks>
        private Collection<string> readLineBuffer = new Collection<string>();

        /// <summary>
        /// This must be set to true when read line method is called so that we don't issue the line user entered as a command
        /// or we try to do auto complete in the mean time.
        /// </summary>
        private bool inReadLine;

        /// <summary>
        /// This variable is set to true if an application is running on the shell right now.
        /// If it is true all user input is buffered and 
        /// </summary>
        private bool isApplicationRunning;

         #endregion 

        #region Private Properties

        /// <summary>
        /// Gets the text for the current command user is typing.
        /// </summary>
        private string CurrentCommandText
        {
            get
            {
                return this.Text.Substring(this.CurrentCommandStartIndex);
            }
        }

        /// <summary>
        /// Gets the current command line including the prefix as well. 
        /// </summary>
        private string CurrentCommandLine
        {
            get
            {
                return this.LastAppendedLine;
            }
        }

        #endregion 

        #region Constructors

        public ShellTextBox()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();
            this.SetupContextMenu();
            commandBrowser = new Browser();
            commandBrowser.UserSelected += new EventHandler(CommandBrowserOnUserSelect);
            matchDirectoryForStrings = new StringCollection();
            String[] myArr = new String[] { "cd"};
            matchDirectoryForStrings.AddRange(myArr);
            this.ApplyChangesFontsAndColors();
        }

        #endregion

        #region Methods - initialization /cleanup

        /// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (this.shellHost != null)
                {
                    this.shellHost.Dispose();
                }
			}
			base.Dispose( disposing );
		}
		
		

		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.Dock = System.Windows.Forms.DockStyle.Fill;
            this.AcceptsTab = true;
            this.Location = new System.Drawing.Point(0, 0);
            this.Multiline = true;
            this.Name = "cmdTextBox1";
            this.Size = new System.Drawing.Size(538, 350);
            this.TabIndex = 0;
            this.ReadOnly = true;
            
            // Register for events
            this.KeyDown += new KeyEventHandler(HandleKeyDown);
            this.KeyPress += new KeyPressEventHandler(this.HandleKeyPressEvent);
            this.KeyUp += new KeyEventHandler(HandleKeyUp);
            Properties.Settings.Default.SettingsSaving += new System.Configuration.SettingsSavingEventHandler(Default_SettingsSaving);
        }

        /// <summary>
        /// Setups the context menu for the tool window. Context menu contains commands to cut/copy/paste text and open options dialog.
        /// </summary>
        private void SetupContextMenu()
        {
            // Create the context menu
            ContextMenu menu = new ContextMenu(new MenuItem[] {
                new MenuItem(Strings.ContextMenu_Cut, new EventHandler(this.CutText), Shortcut.CtrlX),
                new MenuItem(Strings.ContextMenu_Copy, new EventHandler(this.CopyText), Shortcut.CtrlC),
                new MenuItem(Strings.ContextMenu_Paste, new EventHandler(this.PasteText), Shortcut.CtrlV),
                new MenuItem(Strings.ContextMenu_Delete, new EventHandler(this.DeleteSelection), Shortcut.Del),
                new MenuItem(Strings.ContextMenu_Options, new EventHandler(
                    delegate(object o, EventArgs e){
                         extensibilityObject.ExecuteCommand("Tools.Options", String.Empty);
                    })),
                new MenuItem(Strings.ContextMenu_Seperator), // Used as seperator
                new MenuItem(Strings.ContextMenu_Restart, new EventHandler(this.RestartShell))});
            menu.Popup += new EventHandler(CheckContextMenuEntries);
            menu.Collapse += new EventHandler(delegate(object o, EventArgs e)
            {
                this.Cursor = Cursors.IBeam;
            });
            this.ContextMenu = menu;
        }

        /// <summary>
        /// Restarts the shell environment
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RestartShell(object sender, EventArgs e)
        {
            if (!this.InvokeRequired)
            {
                lock (this.syncObject)
                {
                    if (this.shellHost != null)
                    {
                        this.shellHost.Dispose();
                        this.shellHost = null;
                    }
                    this.Clear();
                    this.history.Clear();
                    this.history.ResetList();
                    this.StartShell();
                }
            }
            else
            {
                this.BeginInvoke(new EventHandler(this.RestartShell), sender, e);
            }
        }

        /// <summary>
        /// Determines the starting directory by looking at user's preferences
        /// </summary>
        /// <returns>A valid directory or empty/null information</returns>
        private string DetermineStartingDirectory()
        {
            foreach (StartingDirectoryPreference choice in StartingDirectorySetting.UserSettings.Preferences)
            {
                if (choice == StartingDirectoryPreference.ShellDefault) break;
                string directory = DetermineStartingDirectory(choice);
                if (!String.IsNullOrEmpty(directory)) {
                    return directory;
                }
            }
            return null;
        }

        /// <summary>
        /// Determines the starting directory for the provided preference
        /// </summary>
        /// <param name="preference">Preference to look up</param>
        /// <returns>A valid directory or empty/null information</returns>
        private string DetermineStartingDirectory(StartingDirectoryPreference preference)
        {
            switch (preference)
            {
                case StartingDirectoryPreference.Custom:
                    if (!String.IsNullOrEmpty(StartingDirectorySetting.UserSettings.CustomDirectory) &&
                        Directory.Exists(StartingDirectorySetting.UserSettings.CustomDirectory))
                    {
                        return StartingDirectorySetting.UserSettings.CustomDirectory;
                    }
                    break;
                case StartingDirectoryPreference.ProjectDirectory:
                    /// Try to get current project directory if there is one
                    object[] activeProjects = (object[])(this.ExtensibilityObject.ActiveSolutionProjects);
                    if (activeProjects.Length > 0)
                    {
                        Project activeProject = activeProjects[0] as Project;
                        if (activeProject != null)
                        {
                            try
                            {
                                return Path.GetDirectoryName(activeProject.FileName);
                            }
                            catch (ArgumentException)
                            {
                                return null;
                            }
                            catch (PathTooLongException)
                            {
                                return null;
                            }
                        }
                    }
                    break;
                case StartingDirectoryPreference.SolutionDirectory:
                    Solution currentSolution = this.ExtensibilityObject.Solution;
                    if (currentSolution != null && 
                        !String.IsNullOrEmpty(currentSolution.FileName))
                    {
                        try
                        {
                            return Path.GetDirectoryName(currentSolution.FileName);
                        }
                        catch (ArgumentException)
                        {
                            return null;
                        }
                        catch (PathTooLongException)
                        {
                            return null;
                        }
                    }
                    break;
                default:
                    break;
            }
            return null;
        }

        /// <summary>
        /// Starts the shell process
        /// </summary>
        /// <returns>True if successfull, false otherwise </returns>
        public bool StartShell()
        {
            try
            {
                this.shellHost = ShellLoader.LoadSelectedShell();
                string startingDirectory = DetermineStartingDirectory();
                this.shellHost.InitializeShellProcess(this, startingDirectory);
                return true;
            }
            catch (ShellLoadException ex)
            {
                MessageBox.Show(
                        String.Format(
                            System.Globalization.CultureInfo.CurrentCulture,
                            Strings.ShellLoadError_SelectedShellError,
                            ex.Message),
                        Strings.ShellLoadError_UserMessageCaption,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error,
                        MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.DefaultDesktopOnly);
                return false;
            }
        }

        /// <summary>
        /// Checks context menu entries and enables/disabled them depending on the cursor position, selection and clipboard contents.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CheckContextMenuEntries(object sender, EventArgs e)
        {
            this.Cursor = Cursors.Default;
            this.ContextMenu.MenuItems[0].Enabled = (this.SelectionStart >= this.CurrentCommandStartIndex) &&
                this.SelectionLength > 0;
            this.ContextMenu.MenuItems[1].Enabled = this.SelectionLength > 0;
            this.ContextMenu.MenuItems[2].Enabled = (this.SelectionStart >= this.CurrentCommandStartIndex) &&
                Clipboard.ContainsText();
            this.ContextMenu.MenuItems[3].Enabled = (this.SelectionStart >= this.CurrentCommandStartIndex) &&
                this.SelectionLength > 0;
            
        }

        void Default_SettingsSaving(object sender, CancelEventArgs e)
        {
            this.ApplyChangesFontsAndColors();
        }

        /// <summary>
        /// Applies font settings and colors from the settings file to current window.
        /// </summary>
        private void ApplyChangesFontsAndColors()
		{
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(this.ApplyChangesFontsAndColors));
                return;
            }
			bool oldAutoSize = AutoSize;
			AutoSize = true;

			System.Drawing.Font newFont = System.Windows.Forms.Control.DefaultFont;

            if (Properties.Settings.Default.ConsoleFontUseVS && ExtensibilityObject != null)
            {
                    newFont = Utils.CodeWindowFont;
                    this.BackColor = Utils.CodeWindowBackColor;
                    this.ForeColor = Utils.CodeWindowForeColor;
            }
            else
            {
                newFont = Properties.Settings.Default.ConsoleFont;
                this.ForeColor = Properties.Settings.Default.ConsoleFontColor;
                this.BackColor = Properties.Settings.Default.ConsoleFontBackColor;
            }
            Font = newFont;
            AutoSize = oldAutoSize;
		}

		
		
		#endregion

        #region Methods - Modifying text displayed

        /// <summary>
        /// Copies text from the window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CopyText(object sender, EventArgs e)
        {
            this.Copy();
        }

        /// <summary>
        /// Pastes text from clipboard only if cursor is at a point wher user can type.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PasteText(object sender, EventArgs e)
        {
            if (this.SelectionStart >= this.CurrentCommandStartIndex)
            {
                if (Clipboard.ContainsText())
                {
                    string textToPaste = Clipboard.GetText();
                    this.SelectedText = textToPaste;
                }
                
            }
        }

        /// <summary>
        /// Deletes the selected text if it is modifiable by user
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DeleteSelection(object sender, EventArgs e)
        {
            if (this.SelectionStart >= this.CurrentCommandStartIndex)
            {
                /// We need to set read only to false otherwise selected text can not be changed
                /// to empty text
                this.ReadOnly = false;
                this.SelectedText = String.Empty;
                this.ReadOnly = true;
                UpdateCommandBrowser();
            }
        }

        /// <summary>
        /// Cuts the text and copies it to clipboard only if text is modifiable by user
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CutText(object sender, EventArgs e)
        {
            if (this.SelectionStart >= this.CurrentCommandStartIndex && this.SelectionLength > 0)
            {
                string cutText = this.SelectedText;
                Clipboard.SetText(cutText);
                this.DeleteSelection(sender,e);
            }
        }

        /// <summary>
        /// Adds text to end of the text box, also updates the beginning
        /// of current command line.
        /// </summary>
        /// <param name="text"></param>
        private void AddText(string text)
        {
            if (!String.IsNullOrEmpty(text))
            {
                if (this.InvokeRequired)
                {
                    this.Invoke(
                        new UpdateTextHandler(AddText),
                        new object[] { text });
                }
                else
                {
                    if (this.ClientSize.Width > 0 && this.ClientSize.Height > 0)
                    {
                        this.AppendText(text);
                        UpdateLastLine(text);
                    }
                    else
                    {
                        // The control hasn't been drawn for the first time yet,
                        // buffer the text until after it has been.
                        buffer.Append(text);
                    }
                }
            }
        }

        private void UpdateLastLine(string text)
        {
            if (text.Contains(Environment.NewLine))
            {
                this.LastAppendedLine = text.Substring(
                text.LastIndexOf(Environment.NewLine) + Environment.NewLine.Length);
            }
            else
            {
                this.LastAppendedLine = text;
            }
            this.CurrentCommandStartIndex = this.TextLength;
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            if (this.Size.Width > 0 && this.Size.Height > 0 && buffer.Length > 0)
            {
                string text = buffer.ToString();
                this.AppendText(text);
                this.UpdateLastLine(text);

                // Clear the buffer
                buffer.Remove(0, buffer.Length);
            }
        }

        #endregion

        #region Methods - Displaying Text

        /// <summary>
        /// If provided Visual Studio command is a command requiring a filename, this method
        /// converts the filename to full path since Visual Studio's current directory
        /// may not match the current directory in the shell.
        /// </summary>
        /// <param name="fullCommand"></param>
        /// <returns></returns>
        private string AddDirectoryInformationToCommand(string fullCommand)
		{
			string [] commandWords = fullCommand.ToString().Split();
			string command = commandWords[0].ToLower(System.Globalization.CultureInfo.CurrentUICulture);
			string argument = "";
			if (commandWords.Length > 1)
			{
				argument = commandWords[1];
				if (Properties.Settings.Default.VisualStudioFileCommands.Contains(
                        command.TrimStart(this.shellHost.VisualStudioCommandEscapeCharacter)))
				{
					argument = Path.Combine(this.shellHost.GetCurrentWorkingDirectory(this.CurrentCommandLine),argument);
					return command + " \""+ argument +"\"" ;
				}
			}
			
			return fullCommand;
		}
	
        

		#endregion

		#region Methods - Event Handlers for KeyDown and KeyPress events

        /// <summary>
        /// Handles KeyUp event. Forwards the event to IShellUI.KeyUp event
        /// if command browser is not visible and an application is running.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HandleKeyUp(object sender, KeyEventArgs e)
        {
            lock (this)
            {
                if (!commandBrowser.Visible && this.isApplicationRunning)
                {
                    if (keyUpShellUIStorage != null)
                    {
                        this.keyUpShellUIStorage.Invoke(this, e);
                    }
                }
            }
        }

        /// <summary>
        /// Handles KeyDown event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void HandleKeyDown(object sender, KeyEventArgs e)
        {
            lock (this)
            {
                if (!commandBrowser.Visible && isApplicationRunning)
                {
                    if (keyDownShellUIStorage != null)
                    {
                        keyDownShellUIStorage.Invoke(this, e);
                    }
                    return;
                }
                
                e.Handled = true;
                
                // Handling keys for both command browser and shell text box.
                this.NavKeyDownHandling(sender, e);

                if (commandBrowser.Visible)
                    this.CommandBrowserKeyDownHandling(sender, e);
                else 
                {
                    this.AutoCompleteKeyDownHandling(sender, e);
                    this.CtrlKeyDownHandling(sender, e);
                }
                
            }
		}

        private void NavKeyDownHandling(object sender, KeyEventArgs e)
        {
            switch (e.KeyData)
            {
                case Keys.Delete:
                    if (this.SelectionLength == 0 && this.SelectionStart >= this.CurrentCommandStartIndex &&
                        this.SelectionStart < this.TextLength)
                    {
                        this.Select(this.SelectionStart, 1);
                    }
                    this.DeleteSelection(null, null);
                    e.Handled = true;
                    break;

                case Keys.Back:
                    if (this.SelectionLength == 0 && this.SelectionStart > this.CurrentCommandStartIndex)
                    {
                        this.Select(this.SelectionStart - 1, 1);
                    }
                    this.DeleteSelection(null, null);
                    e.Handled = true;
                    break;

                case Keys.Left:
                    if (this.SelectionStart > this.CurrentCommandStartIndex)
                    {
                        this.Select(this.SelectionStart - 1, 0);
                    }
                    e.Handled = true;
                    break;

                case Keys.Right:
                    if (this.SelectionStart < this.TextLength && this.SelectionStart >= this.CurrentCommandStartIndex)
                    {
                        this.Select(this.SelectionStart + 1, 0);
                    }
                    e.Handled = true;
                    break;

                case Keys.End:
                    this.Select(this.TextLength, 0);
                    e.Handled = true;
                    break;

                case Keys.Home:
                    this.Select(this.CurrentCommandStartIndex, 0);
                    e.Handled = true;
                    break;

                case Keys.Escape:
                    if (!this.commandBrowser.Visible && this.CurrentCommandText.Length > 0)
                    {
                        this.Select(this.CurrentCommandStartIndex,
                            this.CurrentCommandText.Length);
                        this.DeleteSelection(sender, e);
                    }
                    break;

                default:
                    break;
            }
        }

        private void CommandBrowserKeyDownHandling(object sender, KeyEventArgs e)
        {
            switch (e.KeyData)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.PageDown:
                case Keys.PageUp:
                case Keys.Tab:
                case Keys.Tab | Keys.Shift:
                    commandBrowser.MoveSelection(e.KeyData);
                    CommandBrowserOnUserSelect(null, null);
                    break;

                case Keys.Escape:
                    this.CloseCommandBrowser();
                    break;

                case Keys.Space:
                    this.CommandBrowserOnUserSelect(null, null);
                    this.CloseCommandBrowser();
                    break;

                default:
                    break;
            }
        }

        private void AutoCompleteKeyDownHandling(object sender, KeyEventArgs e)
        {
            if (!(e.KeyData == Keys.Tab || (e.KeyData == (Keys.Tab | Keys.Shift))) && inAutoCompleteCycle)
            {
                /// If anything other than Tab or Shift + Tab is pressed, we can no longer be in
                /// auto complete cycle.
                this.inAutoCompleteCycle = false;
            }
            /// Need to handle all key down events to avoid beeping of RichTextBox
            /// because it is set to readonly.
            switch (e.KeyData)
            {
                case Keys.Up:
                    CycleHistory(true);
                    e.Handled = true;
                    break;
                case Keys.Down:
                    CycleHistory(false);
                    e.Handled = true;
                    break;
                case Keys.Tab:
                    CycleAutoComplete(true);
                    this.inAutoCompleteCycle = true;
                    e.Handled = true;
                    break;
                case Keys.Tab | Keys.Shift:
                    CycleAutoComplete(false);
                    this.inAutoCompleteCycle = true;
                    e.Handled = true;
                    break;
            };
        }

        private void CtrlKeyDownHandling(object sender, KeyEventArgs e)
        {
            switch (e.KeyData)
            {
                case Keys.C | Keys.Control:
                    this.CopyText(null, null);
                    break;

                case Keys.X | Keys.Control:
                    this.CutText(null, null);
                    break;

                case Keys.A | Keys.Control:
                case Keys.E | Keys.Control:
                case Keys.L | Keys.Control:
                case Keys.R | Keys.Control:
                    e.Handled = false;
                    break;

                case Keys.V | Keys.Control:
                    this.PasteText(null, null);
                    break;

                default:
                    break;
            }
        }
        
        /// <summary>
        /// Handles key press event. If an application is running on the shell and ReadLine call is not active
        /// key presses are ignored, instead key down/key up event is handled by the shell host.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HandleKeyPressEvent(object sender, KeyPressEventArgs e)
        {
            if (this.SelectionStart >= this.CurrentCommandStartIndex &&
                (!this.isApplicationRunning || this.inReadLine))
            {
                lock (this)
                {
                    if (Char.IsLetterOrDigit(e.KeyChar) ||
                        Char.IsPunctuation(e.KeyChar) ||
                        Char.IsSeparator(e.KeyChar) ||
                        Char.IsSymbol(e.KeyChar))
                    {
                        e.Handled = true;
                        this.SelectedText = e.KeyChar.ToString();
                        UpdateCommandBrowser();
                        if (e.KeyChar == this.shellHost.VisualStudioCommandEscapeCharacter &&
                            this.SelectionStart == this.CurrentCommandStartIndex + 1)
                        {
                            ShowCommandBrowser(true);
                        }
                        
                    }
                    else if (e.KeyChar == (char)13)
                    {
                        this.IssueCommand();
                        e.Handled = true;
                    }

                    if (this.SelectionStart <= this.CommandBrowserStartIndex)
                    {
                        this.CloseCommandBrowser();
                    }
                }
            }

        }

        /// <summary>
        /// Issues to current command to either shell host or Visual Studio.
        /// </summary>
        private void IssueCommand()
        {
            lock (this.syncObject)
            {
                /// Handling enter key
                history.ResetList();
            }
            string command = this.CurrentCommandText;
            this.CloseCommandBrowser();
            if (this.inReadLine)
            {
                /// Send current line to shell host
                lock (this.readLineBuffer) 
                {
                    this.readLineBuffer.Add(command);
                    this.lineCompleted.Set();
                }
                this.inReadLine = false;
                return;
            } else if (command.Trim().Equals("cls", StringComparison.InvariantCultureIgnoreCase)) {
                /// Handle "cls" command specially
                this.Clear();
                this.shellHost.ExecuteCommand(String.Empty);
            }
            else if (command.StartsWith(shellHost.VisualStudioCommandEscapeCharacter.ToString()))
            {
                if (ExtensibilityObject != null)
                {
                    command = this.shellHost.ExpandEnvironmentVariables(command);
                    string vsCommand = this.AddDirectoryInformationToCommand(command.Trim());
                    int spaceIndex = vsCommand.IndexOf(" ");
                    try
                    {
                        if (spaceIndex == -1)
                            ExtensibilityObject.ExecuteCommand(vsCommand.Substring(1, vsCommand.Length - 1), "");
                        else
                            ExtensibilityObject.ExecuteCommand(vsCommand.Substring(1, spaceIndex), vsCommand.Substring(spaceIndex, vsCommand.Length - spaceIndex));
                    }
                    catch (COMException ex)
                    {
                        this.WriteLine(String.Empty);
                        this.WriteLine(ex.Message);
                    }
                    finally
                    {
                        this.AddText(Environment.NewLine);
                        this.shellHost.ExecuteCommand("");
                    }
                }
            }
            else
            {
                AddText(Environment.NewLine);
                this.shellHost.ExecuteCommand(command);
            }
            if (!String.IsNullOrEmpty(command))
            {
                lock (this.syncObject)
                {
                    history.AddFirst(command);
                    history.ResetPosition();
                }
            }
        }
		#endregion

		#region Methods - AutoComplete and History management

		/// <summary>
		/// Gets the next or previous entry from the auto complete list  
		/// </summary>
		/// <param name="forward">True if the next entry should be retrieved</param>
        private void CycleAutoComplete(bool forward)
		{
			bool isCDCase = false;

            //TODO: Replace with smart split (takes care of the quotes as well)
            int lastArgumentIndex = 0;
            string[] selectedWords = Utils.SplitCommand(this.CurrentCommandText, out lastArgumentIndex);
			string selectedCommand = (selectedWords.Length > 1) ? selectedWords[0] : null;
			string selectedArgument = "";
            string selectedFilteredArgument = "";
            string directoryToUse;

            if (selectedWords.Length > 0)
			{
                selectedArgument = selectedWords[selectedWords.Length - 1];
                selectedFilteredArgument = selectedWords[selectedWords.Length - 1].Replace("\"", String.Empty);

			}

            directoryToUse = this.GetAutoCompleteDir(ref selectedFilteredArgument);
            
            if (directoryToUse == null)
            {
                return;
            }

            int constantPartLength = lastArgumentIndex; // this.CurrentCommandText.Length - selectedArguement.Length;
			int specialCaseIndex = matchDirectoryForStrings.IndexOf(selectedCommand);

			if ( specialCaseIndex!= -1)
			{
				isCDCase = true;
			}

            VSCmdShell.AutoCompleteCollection.AutoCompleteTypes searchFlag = isCDCase ? AutoCompleteCollection.AutoCompleteTypes.Directories : AutoCompleteCollection.AutoCompleteTypes.Both;
			/// If this is first time user is pressing Tab after other keys, setup the auto complete list.
            if (!this.inAutoCompleteCycle)
            {
                this.commandBrowser.Items.Clear();
                if (autoList == null)
                {
                    this.autoList = new AutoCompleteCollection(directoryToUse, selectedFilteredArgument, searchFlag);
                }
                else
                {
                    autoList.SetupList(directoryToUse, selectedFilteredArgument, searchFlag);
                }

                /* Using command browser
                if (autoList.Count > 0)
                {
                    string prefix = String.Empty;
                    if (selectedArgument.IndexOf(Path.DirectorySeparatorChar) >= 0)
                    {
                        prefix = selectedArgument.Substring(0, selectedArgument.LastIndexOf(Path.DirectorySeparatorChar)) +
                            Path.DirectorySeparatorChar;
                    }
                    
                    foreach (System.IO.FileSystemInfo entry in autoList)
                    {
                        if (!String.IsNullOrEmpty(entry.Name))
                        {
                            string value = prefix + entry.Name;
                            this.commandBrowser.Items.Add(value);
                        }
                    }
                    int oldSelection = this.SelectionStart;
                    Select(TextLength - this.CurrentCommandText.Length + constantPartLength, 0);
                    this.ShowCommandBrowser(false);
                    Select(oldSelection, 0);
                }
                */
            }

            string last = autoList.GetEntry(forward);

            if (!String.IsNullOrEmpty(last))
            {
                /// Auto complete entry returns only the filename or directory name.
                /// If the argument being replaced is actually a path, only
                /// the last part of the path has to be replaced.
                Select(TextLength - this.CurrentCommandText.Length + constantPartLength, selectedArgument.Length);
                if (selectedArgument.IndexOf(Path.DirectorySeparatorChar) >= 0)
                {
                    last = selectedArgument.Substring(0, selectedArgument.LastIndexOf(Path.DirectorySeparatorChar)) +
                        Path.DirectorySeparatorChar + last;
                }
                SelectedText = last;
            }
        }

        /// <summary>
        /// Gets the current directory to look for auto complete entries
        /// </summary>
        /// <param name="argument">Argument to be replaced</param>
        /// <returns>Full directory path (it can be a network link: \\server\directory\), null is returned if auto complete
        /// operation should be aborted</returns>
        private string GetAutoCompleteDir(ref string argument)
        {
            string directory;
            if (String.IsNullOrEmpty(argument))
            {
                directory = this.shellHost.GetCurrentWorkingDirectory(this.CurrentCommandLine);
            }
            else
            {
                //Check if argument is a UNC patch
                if (argument.StartsWith(@"\\"))
                {
                    if (argument.Length == 2 || argument.IndexOf(@"\", 2) < 0)
                    {
                        directory = null;
                    }
                    else
                    {
                        directory = Path.GetDirectoryName(argument);
                        argument = Path.GetFileName(argument);
                    }
                }
                //Argument is a regular path
                else
                {
                    directory = Path.GetDirectoryName(argument);
                    /// If directory is null, it means it is already rooted
                    if (directory != null)
                    {
                        if (directory.StartsWith(@"\"))
                        {
                            directory = Path.Combine(Path.GetPathRoot(this.shellHost.GetCurrentWorkingDirectory(this.CurrentCommandLine)), directory);
                        }
                        else
                        {
                            directory = Path.Combine(this.shellHost.GetCurrentWorkingDirectory(this.CurrentCommandLine), directory);
                        }
                    }
                    else
                    {
                        directory = argument;
                    }
                    argument = Path.GetFileName(argument);
                    
                }
            }
            return directory;
        }

        /// <summary>
        /// Cycles the command history and replaces the current command with the next entry.
        /// </summary>
        /// <param name="getNext">set to True if history should be cycled in forward direction.</param>
		private void CycleHistory(bool getNext)
		{
            lock (this.syncObject)
            {
                Select(TextLength - this.CurrentCommandText.Length, this.CurrentCommandText.Length);
                string last = history.GetHistoryEntry(getNext);
                SelectedText = last;
		    }
        }

		#endregion

		#region Methods - Intellisense browser

        /// <summary>
        /// Filters the entries in command browser to match what user is typing.
        /// </summary>
        private void UpdateCommandBrowser()
        {
            if (this.commandBrowser.Visible)
            {
                commandBrowser.Filter(Text.Substring(CommandBrowserStartIndex));
            }
        }

        /// <summary>
        /// Shows the command browser to user.
        /// </summary>
        /// <param name="afterKeyPress">If true, the last character will become part of the text user is completing.</param>
        private void ShowCommandBrowser(bool afterKeyPress)
        {
            if (commandBrowser != null)
            {
                Point browserLocation = GetPositionFromCharIndex(this.TextLength - 1);
                browserLocation = this.PointToScreen(browserLocation);
                
                if (commandBrowser.Items.Count == 0)
                {
                    foreach (Command cmd in this.ExtensibilityObject.Commands)
                    {
                        if (!String.IsNullOrEmpty(cmd.Name))
                        {
                            this.commandBrowser.Items.Add(this.shellHost.VisualStudioCommandEscapeCharacter + cmd.Name);
                        }
                    }
                }
                this.CommandBrowserStartIndex = this.SelectionStart;
                if (afterKeyPress) this.CommandBrowserStartIndex--;
                commandBrowser.ShowBrowser(browserLocation, this.FontHeight);
                this.UpdateCommandBrowser();
                this.Focus();
            }
        }

        /// <summary>
        /// This method is invoked when user selects an item in the command browser. Selected text
	/// is shown on the shell window as well replacing what user was completing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
		private void CommandBrowserOnUserSelect(object sender, EventArgs e)
		{
            if (this.CommandBrowserStartIndex < this.TextLength)
            {
                Select(this.CommandBrowserStartIndex, TextLength - this.CommandBrowserStartIndex);
                string selectedString = commandBrowser.SelectedString;
                if (!selectedString.StartsWith(shellHost.VisualStudioCommandEscapeCharacter.ToString()) &&
                    selectedString.Contains(" "))
                {
                    selectedString = selectedString.Replace("\"", String.Empty);
                    selectedString = "\"" + selectedString + "\"";
                }
                SelectedText = selectedString;
            }
		}

        /// <summary>
        /// Closes the instelli sense browser window.
        /// </summary>
        public void CloseCommandBrowser()
        {
            commandBrowser.Visible = false;
        }

		#endregion

        #region IShellUI Members

        private event KeyEventHandler keyDownShellUIStorage;
        private event KeyEventHandler keyUpShellUIStorage;

        event KeyEventHandler IShellUI.KeyDown
        {
            add { this.keyDownShellUIStorage += value; }
            remove {
                this.keyDownShellUIStorage -= value;
            }
        }

        event KeyEventHandler IShellUI.KeyUp
        {
            add {
                this.keyUpShellUIStorage += value;
            }
            remove {
                this.keyUpShellUIStorage -= value;
            }
        }

        public bool ApplicationRunning
        {
            set {
                this.isApplicationRunning = value;
            }
            get
            {
                return this.isApplicationRunning;
            }
        }

        public int WindowHeight
        {
            get
            {
                return this.Height / this.Font.Height;
            }
        }

        public int WindowWidth
        {
            get
            {
                using (Graphics g = this.CreateGraphics())
                {
                    SizeF fontSize = g.MeasureString(Strings.FontWidthMeasureString, this.Font);
                    return (int)((float)this.Width * (float)(Strings.FontWidthMeasureString.Length)/ fontSize.Width);
                }
            }
        }

        public void WriteLine(string value)
        {
            lock (this.syncObject)
            {
                this.AddText(value + Environment.NewLine);
            }
        }

        public void Write(string value)
        {
            lock (this.syncObject)
            {
                this.AddText(value);
            }
        }

        /// <summary>
        /// Reads a new line from user input. This method will block until a new line is entered.
        /// </summary>
        /// <returns>Line entered by user</returns>
        /// <exception cref="ShellHostException"></exception>
        public string ReadLine()
        {
            this.inReadLine = true;
            this.lineCompleted.WaitOne();
            this.lineCompleted.Reset();
            lock (this.readLineBuffer)
            {
                if (this.readLineBuffer.Count > 0)
                {
                    string lastValue = this.readLineBuffer[0];
                    this.readLineBuffer.RemoveAt(0);
                    return lastValue;
                }
                else
                {
                    throw new ShellHostException();
                }
            }
        }

        #endregion
  
    }
}
